//
// TypeReference.cs
//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// Copyright (c) 2008 - 2011 Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;

using Mono.Cecil.Metadata;
using Mono.Collections.Generic;
using System.Diagnostics;
using System.Collections.Generic;

namespace Mono.Cecil {

	public enum MetadataType : byte {
		Void = ElementType.Void,
		Boolean = ElementType.Boolean,
		Char = ElementType.Char,
		SByte = ElementType.I1,
		Byte = ElementType.U1,
		Int16 = ElementType.I2,
		UInt16 = ElementType.U2,
		Int32 = ElementType.I4,
		UInt32 = ElementType.U4,
		Int64 = ElementType.I8,
		UInt64 = ElementType.U8,
		Single = ElementType.R4,
		Double = ElementType.R8,
		String = ElementType.String,
		Pointer = ElementType.Ptr,
		ByReference = ElementType.ByRef,
		ValueType = ElementType.ValueType,
		Class = ElementType.Class,
		Var = ElementType.Var,
		Array = ElementType.Array,
		GenericInstance = ElementType.GenericInst,
		TypedByReference = ElementType.TypedByRef,
		IntPtr = ElementType.I,
		UIntPtr = ElementType.U,
		FunctionPointer = ElementType.FnPtr,
		Object = ElementType.Object,
		MVar = ElementType.MVar,
		RequiredModifier = ElementType.CModReqD,
		OptionalModifier = ElementType.CModOpt,
		Sentinel = ElementType.Sentinel,
		Pinned = ElementType.Pinned,
	}

    [DebuggerDisplay("{FullName}")]
    public class TypeReference : MemberReference, IGenericParameterProvider, IGenericContext
    {

        string @namespace;
        bool value_type;
        internal IMetadataScope scope;
        internal ModuleDefinition module;
        private string _AssemblyQualifiedName;

        internal ElementType etype = ElementType.None;

        string fullname;

        protected Collection<GenericParameter> generic_parameters;


        protected TypeReference()
        {

        }

        protected TypeReference(string @namespace, string name)
            : base(name)
        {
            this.@namespace = @namespace ?? string.Empty;
            this.token = new MetadataToken(TokenType.TypeRef, 0);
        }

        public TypeReference(string @namespace, string name, ModuleDefinition module, IMetadataScope scope)
            : this(@namespace, name)
        {
            this.module = module;
            this.scope = scope;
        }

        public TypeReference(string @namespace, string name, ModuleDefinition module, IMetadataScope scope, bool valueType) :
            this(@namespace, name, module, scope)
        {
            value_type = valueType;
        }


        public override string Name
        {
            get { return base.Name; }
            set
            {
                base.Name = value;
                fullname = null;
            }
        }

        public virtual string Namespace
        {
            get { return @namespace; }
            set
            {
                @namespace = value;
                fullname = null;
            }
        }

        public virtual bool IsValueType
        {
            get { return value_type; }
            set { value_type = value; }
        }

        public override ModuleDefinition Module
        {
            get
            {
                if (module != null)
                    return module;

                var declaring_type = this.DeclaringType;
                if (declaring_type != null)
                    return declaring_type.Module;

                return null;
            }
        }

        IGenericParameterProvider IGenericContext.Type { get { return this; } }

        IGenericParameterProvider IGenericContext.Method { get { return null; } }

        GenericParameterType IGenericParameterProvider.GenericParameterType { get { return GenericParameterType.Type; } }

        public virtual bool HasGenericParameters { get { return !generic_parameters.IsNullOrEmpty(); } }

        public virtual Collection<GenericParameter> GenericParameters
        {
            get
            {
                if (generic_parameters != null)
                    return generic_parameters;

                return generic_parameters = new GenericParameterCollection(this);
            }
        }

        public virtual IMetadataScope Scope
        {
            get
            {
                var declaring_type = this.DeclaringType;
                if (declaring_type != null)
                    return declaring_type.Scope;

                return scope;
            }
        }

        public virtual bool IsNested { get { return this.DeclaringType != null; } }

        public override TypeReference DeclaringType
        {
            get { return base.DeclaringType; }
            set
            {
                base.DeclaringType = value;
                fullname = null;
            }
        }

        public override string FullName
        {
            get
            {
                if (fullname != null)
                    return fullname;

                if (IsNested)
                    return fullname = DeclaringType.FullName + "/" + Name;

                if (string.IsNullOrEmpty(@namespace))
                    return fullname = Name;

                return fullname = @namespace + "." + Name;
            }
        }

        public virtual bool IsByReference { get { return false; } }

        public virtual bool IsPointer { get { return false; } }

        public virtual bool IsSentinel { get { return false; } }

        public virtual bool IsArray { get { return false; } }

        public virtual bool IsGenericParameter { get { return false; } }

        public virtual bool IsGenericInstance { get { return false; } }

        public virtual bool IsRequiredModifier { get { return false; } }

        public virtual bool IsOptionalModifier { get { return false; } }

        public virtual bool IsPinned { get { return false; } }

        public virtual bool IsFunctionPointer { get { return false; } }


        public virtual bool IsPrimitive
        {
            get
            {
                switch (etype)
                {
                    case ElementType.Boolean:
                    case ElementType.Char:
                    case ElementType.I:
                    case ElementType.U:
                    case ElementType.I1:
                    case ElementType.U1:
                    case ElementType.I2:
                    case ElementType.U2:
                    case ElementType.I4:
                    case ElementType.U4:
                    case ElementType.I8:
                    case ElementType.U8:
                    case ElementType.R4:
                    case ElementType.R8:
                        return true;
                    default:
                        return false;
                }
            }
        }


        public virtual MetadataType MetadataType
        {
            get
            {
                switch (etype)
                {
                    case ElementType.None:
                        return IsValueType ? MetadataType.ValueType : MetadataType.Class;
                    default:
                        return (MetadataType)etype;
                }
            }
        }


        public virtual TypeReference GetElementType() { return this; }

        public virtual TypeDefinition Resolve()
        {
            var module = this.Module;
            if (module == null)
                throw new NotSupportedException();

            return module.Resolve(this);
        }


        public override System.Reflection.MemberTypes MemberType { get { return System.Reflection.MemberTypes.TypeInfo; } }


        public override int GetHashCode()
        {
            int r = this.Module.Assembly.Name.Name.GetHashCode();
            return r;
        }

        public override bool Equals(object obj)
        {
            bool r = base.Equals(obj);

            if (!r)
            {
                TypeReference Ref = obj as TypeReference;
                if (Ref != null)
                {

                    if (Ref.FullName == this.FullName)
                    {
                        TypeDefinition tt = Ref.Resolve();

                        r = (tt.Module.Assembly.Name.Name == this.Module.Assembly.Name.Name);
                    }

                }
            }

            return r;

        }



        public virtual string AssemblyQualifiedName
        {

            get
            {

                if (string.IsNullOrEmpty(_AssemblyQualifiedName))
                {

                    TypeReference type = this;

                    if (type == null)
                        return string.Empty;

                    System.Text.StringBuilder s = new System.Text.StringBuilder();

                    if (!string.IsNullOrEmpty(type.Namespace))
                    {
                        s.Append(type.Namespace);
                        s.Append(".");
                    }
                    s.Append(type.Name);

                    GenericInstanceType i = type as GenericInstanceType;

                    if (i != null)
                    {
                        s.Append("[");

                        foreach (TypeReference item in i.GenericArguments)
                        {
                            string a = item.AssemblyQualifiedName;
                            s.Append("[");
                            s.Append(a);
                            s.Append("]");
                        }

                        s.Append("]");
                    }

                    TypeDefinition def = type.Resolve();
                    if (def != null)
                    {
                        AssemblyDefinition assembly = def.Module.Assembly;
                        string nn = assembly.Name.Name;
                        if (nn != "mscorlib")
                            s.Append(", " + nn);

                    }
                    else
                    {
                        AssemblyDefinition assembly = type.Module.Assembly;
                        string nn = assembly.Name.Name;
                        if (nn != "mscorlib")
                            s.Append(", " + nn);
                    }

                    _AssemblyQualifiedName = s.ToString();

                }

                return _AssemblyQualifiedName;

            }
        }

        public TypeReference GetInterface(string nameInterface, bool ignoreCase)
        {

            if (ignoreCase)
                nameInterface = nameInterface.ToLower();

            foreach (TypeReference item in GetInterfaces())
            {

                string name = nameInterface.Contains(".") ? item.FullName : item.Name;

                if (ignoreCase)
                    name = name.ToLower();

                if (name == nameInterface)
                    return item;

            }

            return null;

        }

        public IEnumerable<TypeReference> GetInterfaces()
        {

            TypeDefinition type = Resolve();

            foreach (TypeReference item in type.Interfaces)
            {
                yield return item;

                TypeDefinition t = item.Resolve();

                if (t != null)
                    foreach (TypeReference i2 in t.Interfaces)
                        yield return i2;

            }

            if (type.BaseType != null)
                foreach (var item in type.BaseType.GetInterfaces())
                    yield return item;
                    
        }

        public bool IsEnumerable { get { return GetInterface("IEnumerable", true) != null; } }

        public virtual IEnumerable<MemberReference> GetMembers()
        {

            System.Reflection.MemberTypes memberType = System.Reflection.MemberTypes.Field | System.Reflection.MemberTypes.Method | System.Reflection.MemberTypes.Property;
            foreach (var item in GetMembers(memberType, string.Empty, this))
                yield return item;

        }

        public virtual IEnumerable<MemberReference> GetMembers(System.Reflection.MemberTypes memberType)
        {

            foreach (var item in GetMembers(memberType, string.Empty, this))
                yield return item;

        }

        public virtual IEnumerable<MemberReference> GetMembers(string name)
        {
            System.Reflection.MemberTypes memberType = System.Reflection.MemberTypes.Field | System.Reflection.MemberTypes.Method | System.Reflection.MemberTypes.Property;
            foreach (var item in GetMembers(memberType, name, this))
                yield return item;

        }

        public virtual IEnumerable<MemberReference> GetMembers(System.Reflection.MemberTypes memberType, string name)
        {

            foreach (var item in GetMembers(memberType, name, this))
                yield return item;

        }

        public virtual IEnumerable<MemberReference> GetMembers(System.Reflection.MemberTypes memberType, string name, TypeReference Sealedtype)
        {

            TypeDefinition type = null;

            type = Resolve();


            if (Mixin.GetAttributes((uint)memberType, (uint)System.Reflection.MemberTypes.Property))
                foreach (PropertyDefinition item in type.Properties)
                    if (string.IsNullOrEmpty(name) || name == item.Name)
                        yield return new PropertyDefinitionRuntime(item, Sealedtype);

            if (Mixin.GetAttributes((uint)memberType, (uint)System.Reflection.MemberTypes.Field))
                foreach (FieldDefinition item in type.Fields)
                    if (string.IsNullOrEmpty(name) || name == item.Name)
                        yield return new FieldDefinitionRuntime(item, Sealedtype);

            if (Mixin.GetAttributes((uint)memberType, (uint)System.Reflection.MemberTypes.Event))
                foreach (EventDefinition item in type.Events)
                    if (string.IsNullOrEmpty(name) || name == item.Name)
                        yield return item;

            if (Mixin.GetAttributes((uint)memberType, (uint)System.Reflection.MemberTypes.Method))
                foreach (MethodDefinition item in type.Methods)
                    if (string.IsNullOrEmpty(name) || name == item.Name)
                        yield return item;

            if (type.BaseType != null)
            {

                TypeReference t2 = type.BaseType;

                if (t2 != null)
                {
                    var t3 = t2.Resolve();
                    if (t3 != null)
                        foreach (MemberReference item in t3.GetMembers(memberType, name, Sealedtype))
                            yield return item;
                }

            }

        }

    }

	static partial class Mixin {

		public static bool IsTypeOf (this TypeReference self, string @namespace, string name)
		{
			return self.Name == name
				&& self.Namespace == @namespace;
		}

		public static bool IsTypeSpecification (this TypeReference type)
		{
			switch (type.etype) {
			case ElementType.Array:
			case ElementType.ByRef:
			case ElementType.CModOpt:
			case ElementType.CModReqD:
			case ElementType.FnPtr:
			case ElementType.GenericInst:
			case ElementType.MVar:
			case ElementType.Pinned:
			case ElementType.Ptr:
			case ElementType.SzArray:
			case ElementType.Sentinel:
			case ElementType.Var:
				return true;
			}

			return false;
		}

		public static TypeDefinition CheckedResolve (this TypeReference self)
		{
			var type = self.Resolve ();
			if (type == null)
				throw new ResolutionException (self);

			return type;
		}
	}

}
